from io import StringIO

from django.urls import path
from django.core.management import CommandError, call_command
from django.http import HttpResponse
from django.test import TestCase
from django.test.utils import override_settings
from django.views.generic.base import View

from unittest.mock import Mock, patch


def function_based_view(request):
    pass


class ClassView(View):
    pass


urlpatterns = [
    path("lambda/view", lambda request: HttpResponse("OK")),
    path("function/based/", function_based_view, name="function-based-view"),
    path("class/based/", ClassView.as_view(), name="class-based-view"),
]


class ShowUrlsExceptionsTests(TestCase):
    """Tests if show_urls command raises exceptions."""

    def test_should_raise_CommandError_when_format_style_does_not_exists(self):
        with self.assertRaisesRegex(
            CommandError,
            "Format style 'invalid_format' does not exist. Options: aligned, dense, json, pretty-json, table, verbose",
        ):
            call_command("show_urls", "--format=invalid_format")

    def test_should_raise_CommandError_when_doesnt_have_urlconf_attr(self):
        with self.assertRaisesRegex(
            CommandError,
            'Settings module <Settings "tests.testapp.settings"> does not have the attribute INVALID_URLCONF.',
        ):
            call_command("show_urls", "--urlconf=INVALID_URLCONF")

    @override_settings(INVALID_URLCONF="")
    def test_should_raise_CommandError_when_doesnt_have_urlconf_attr_print_exc(self):
        m_traceback = Mock()
        with self.assertRaisesRegex(
            CommandError, "Error occurred while trying to load : Empty module name"
        ):
            with patch.dict("sys.modules", traceback=m_traceback):
                call_command("show_urls", "--urlconf=INVALID_URLCONF", "--traceback")

        self.assertTrue(m_traceback.print_exc.called)


@override_settings(ROOT_URLCONF="tests.management.commands.test_show_urls")
class ShowUrlsTests(TestCase):
    @patch("sys.stdout", new_callable=StringIO)
    def test_should_show_urls_unsorted_but_same_order_as_found_in_url_patterns(
        self, m_stdout
    ):
        call_command("show_urls", "-u", verbosity=3)

        lines = m_stdout.getvalue().splitlines()
        self.assertIn(
            "/lambda/view\ttests.management.commands.test_show_urls.<lambda>", lines[0]
        )
        self.assertIn(
            "/function/based/\ttests.management.commands.test_show_urls.function_based_view\tfunction-based-view",
            lines[1],
        )
        self.assertIn(
            "/class/based/\ttests.management.commands.test_show_urls.ClassView\tclass-based-view",
            lines[2],
        )

    @patch("sys.stdout", new_callable=StringIO)
    def test_should_show_urls_sorted_alphabetically(self, m_stdout):
        call_command("show_urls", verbosity=3)

        lines = m_stdout.getvalue().splitlines()
        self.assertEqual(
            "/class/based/\ttests.management.commands.test_show_urls.ClassView\tclass-based-view",
            lines[0],
        )
        self.assertEqual(
            "/function/based/\ttests.management.commands.test_show_urls.function_based_view\tfunction-based-view",
            lines[1],
        )
        self.assertEqual(
            "/lambda/view\ttests.management.commands.test_show_urls.<lambda>", lines[2]
        )

    @patch("sys.stdout", new_callable=StringIO)
    def test_should_show_urls_in_json_format(self, m_stdout):
        call_command("show_urls", "--format=json")

        self.assertJSONEqual(
            m_stdout.getvalue(),
            [
                {
                    "url": "/lambda/view",
                    "module": "tests.management.commands.test_show_urls.<lambda>",
                    "name": "",
                    "decorators": "",
                },
                {
                    "url": "/function/based/",
                    "module": "tests.management.commands.test_show_urls.function_based_view",
                    "name": "function-based-view",
                    "decorators": "",
                },
                {
                    "url": "/class/based/",
                    "module": "tests.management.commands.test_show_urls.ClassView",
                    "name": "class-based-view",
                    "decorators": "",
                },
            ],
        )
        self.assertEqual(len(m_stdout.getvalue().splitlines()), 1)

    @patch("sys.stdout", new_callable=StringIO)
    def test_should_show_urls_in_pretty_json_format(self, m_stdout):
        call_command("show_urls", "--format=pretty-json")

        self.assertJSONEqual(
            m_stdout.getvalue(),
            [
                {
                    "url": "/lambda/view",
                    "module": "tests.management.commands.test_show_urls.<lambda>",
                    "name": "",
                    "decorators": "",
                },
                {
                    "url": "/function/based/",
                    "module": "tests.management.commands.test_show_urls.function_based_view",
                    "name": "function-based-view",
                    "decorators": "",
                },
                {
                    "url": "/class/based/",
                    "module": "tests.management.commands.test_show_urls.ClassView",
                    "name": "class-based-view",
                    "decorators": "",
                },
            ],
        )
        self.assertEqual(len(m_stdout.getvalue().splitlines()), 20)

    @patch("sys.stdout", new_callable=StringIO)
    def test_should_show_urls_in_table_format(self, m_stdout):
        call_command("show_urls", "--format=table")

        self.assertIn(
            "/class/based/    | tests.management.commands.test_show_urls.ClassView           | class-based-view    |",
            m_stdout.getvalue(),
        )
        self.assertIn(
            "/function/based/ | tests.management.commands.test_show_urls.function_based_view | function-based-view |",
            m_stdout.getvalue(),
        )
        self.assertIn(
            "/lambda/view     | tests.management.commands.test_show_urls.<lambda>            |                     |",
            m_stdout.getvalue(),
        )

    @patch("sys.stdout", new_callable=StringIO)
    def test_should_show_urls_in_aligned_format(self, m_stdout):
        call_command("show_urls", "--format=aligned")

        lines = m_stdout.getvalue().splitlines()
        self.assertEqual(
            "/class/based/      tests.management.commands.test_show_urls.ClassView             class-based-view      ",
            lines[0],
        )
        self.assertEqual(
            "/function/based/   tests.management.commands.test_show_urls.function_based_view   function-based-view   ",
            lines[1],
        )
        self.assertEqual(
            "/lambda/view       tests.management.commands.test_show_urls.<lambda>                                    ",
            lines[2],
        )

    @patch("sys.stdout", new_callable=StringIO)
    def test_should_show_urls_with_no_color_option(self, m_stdout):
        call_command("show_urls", "--no-color")

        lines = m_stdout.getvalue().splitlines()
        self.assertEqual(
            "/class/based/\ttests.management.commands.test_show_urls.ClassView\tclass-based-view",
            lines[0],
        )
        self.assertEqual(
            "/function/based/\ttests.management.commands.test_show_urls.function_based_view\tfunction-based-view",
            lines[1],
        )
        self.assertEqual(
            "/lambda/view\ttests.management.commands.test_show_urls.<lambda>", lines[2]
        )
